/*
*	Project: Aethon
*
*	File: ModelPart.cpp
*
*	Author: Robert Whiting
*
*	Date: 2/11/2009
*
*	Purpose: The model part contains relative positions and rotations to display
*	objects other than the terrain. Each model may contain zero or more sub
*	model parts, thus forming a tree structure.
*/

#include "../include/ModelPart.h"
using namespace std;

void ModelPart::load(string filename){
	fprintf(stderr,"ModelPart::load '%s'\n",filename.c_str());
	name = filename;
	filename = "../data/models/" + filename + ".mp";
	ifstream inData;
	inData.open(filename.c_str());
	string token;
    if(!inData)
    	printf("Data file failed\n");
    char comment = inData.peek();
    //read all comments
    while(comment == '#'){
		inData.ignore(100,'\n');
		comment = inData.peek();
    }
    //position
	inData >> relativePosition.v[0] >> relativePosition.v[1] >> relativePosition.v[2];
	//rotation
	inData >> relativeRotation.v[0] >> relativeRotation.v[1] >> relativeRotation.v[2];
	inData >> token;
	if(token == "t"){
		inData >> token;
		texImage.name = token;
		processImage(texImage.name);
		inData >> token;
	}
	//aobject name
	string aobjFilename = token;
	modelPartId = buildModelPart(aobjFilename);
	inData >> token;
	int childCount = 0;
	//load children
    while(!inData.eof()){
    	//fprintf(stderr,"\tnew child of '%s' : '%s'\n",filename.c_str(), token.c_str());
		modelPartList.push_back(new ModelPart());
		modelPartList[childCount]->load(token);
		inData >> token;
		childCount++;
    }
    inData.close();
    printf("Loading '%s' complete\n",filename.c_str());
}//end load


//########################################################
double ModelPart::normal(const double a[3], const double b[3], const double c[3], double d[3]) {
    double dist;
    d[0] = (b[1]-a[1])*(c[2]-a[2]) - (b[2]-a[2])*(c[1]-a[1]);
    d[1] = (b[2]-a[2])*(c[0]-a[0]) - (b[0]-a[0])*(c[2]-a[2]);
    d[2] = (b[0]-a[0])*(c[1]-a[1]) - (b[1]-a[1])*(c[0]-a[0]);

    dist = sqrt(d[0]*d[0] + d[1]*d[1] + d[2]*d[2]);
    d[0] /= dist;
    d[1] /= dist;
    d[2] /= dist;
    return dist;
}

//########################################################
vector<Material> ModelPart::readMaterials(string basefile) {
	fprintf(stderr,"ModelPart::readMaterials '%s'\n", basefile.c_str());
    string filename = "../data/models/" + basefile + ".mat";
    ifstream matFile;
    vector<Material> mats;
    string name;
    Material *newMat;
    matFile.open(filename.c_str());
    if(!matFile){        // file doesn't exist
    	printf("Material file not found!\n");
        return mats;    // no mats for this obj
    }
    matFile >> name;
    while(!matFile.eof()) {
        name = name.substr(0,name.size() - 1); // remove colon
        newMat = new Material();
        newMat->name = name;
        matFile >> newMat->amb;     // read in ambient ratio
        matFile >> newMat->diff[0] >> newMat->diff[1] >> newMat->diff[2];
        matFile >> newMat->diff[3];   // skip the alpha value, it isn't used
        matFile >> newMat->spec[0] >> newMat->spec[1] >> newMat->spec[2];
        newMat->spec[3] = 1.0f;
        matFile >> newMat->shine;
        mats.push_back(*newMat);
        matFile.ignore(100,'\n');
        matFile >> name;
    }
    return mats;
}

//########################################################
void ModelPart::setMaterial(string matName, vector<Material> &mats) {
    Material mat;
    float amb[4];
    for(int i = 0; i < (int)mats.size(); i++) { // find mat
        if(mats[i].name.compare(matName) == 0) {
            mat = mats[i];
            break;
        }
    }

    amb[0] = amb[1] = amb[2] = amb[3] = mat.amb;
    glColor3fv(mat.diff);
    glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT, amb);
    glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE, mat.diff);
    glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR, mat.spec);
    glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS, mat.shine);
}

//########################################################
int ModelPart::buildModelPart(string basefile) {
    string filename = "../data/models/" + basefile + ".aobj";
	fprintf(stderr,"ModelPart::buildModelPart '%s'\n",filename.c_str());
    ifstream inFile;
    string token;
    vector<Material> mats = readMaterials(basefile);
    inFile.open(filename.c_str());
    if(!inFile)
    	printf("File failed\n");
    inFile >> token;

    // there is only one object in this file
    if(token.compare("v") == 0 || token.compare("t") == 0) {
        inFile.seekg(0, ios::beg);
        processObj(inFile,mats);
    }
    else {
    	fprintf(stderr,"Fail: aobj file contains more than one object\n");
    }
    inFile.close();
    return modelPartId;
}

//########################################################
void ModelPart::processObj(ifstream &in, vector<Material> &mats) {
    string token;
    int a,b,c,d;
    double x,y,z,nx,ny,nz,tx,ty;
    vector<vector3f> verts;
    vector<vector3f> textures;
    bool hasTexture = false;

    verts.reserve(500);
    textures.reserve(500);
    in >> token;
    if(token.compare("t") == 0) hasTexture = true;
        
    while(!in.eof()) {
    	if(token.compare("v") == 0) {
            in >> x >> y >> z;
            in >> nx >> ny >> nz;
            in.ignore(10,'\n');
            verts.push_back(vector3f(x,y,z,nx,ny,nz));
        }else if(token.compare("t") == 0) {
        	in >> tx >> ty >> token;
            in >> x >> y >> z;
            in >> nx >> ny >> nz;
            in.ignore(10,'\n');
			//fprintf(stderr,"Pushing t %f %f\n",tx,ty);
			// fprintf(stderr,"Pushing v %f %f %f\n",x,y,z);
            verts.push_back(vector3f(x,y,z,nx,ny,nz));
            textures.push_back(vector3f(tx,ty,0));
        }else if(token.compare("f") == 0 || token.compare("MATERIAL") == 0)
            break;
        else
            fprintf(stderr,"Invalid syntax reading vector3fs: '%s'.", token.c_str());
        in >> token;
    }
    fprintf(stderr,"size of verts %d and textures %d\n",verts.size(),textures.size());
    modelPartId = glGenLists(1);
    fprintf(stderr,"new modelPartId: %d\n",getModelPartId());
    glNewList(modelPartId,GL_COMPILE);
    glPushMatrix();
    if(hasTexture) loadTexture();
    while(!in.eof()) {      // read in faces
    	string out;
        if(token.compare("f") == 0) {
            in >> a >> b >> c;
            glBegin(GL_POLYGON);
            	if(hasTexture) glTexCoord2f(textures[a].v[0],textures[a].v[1]);
                glNormal3fv(verts[a].vn);
                glVertex3fv(verts[a].v);
            	if(hasTexture) glTexCoord2f(textures[b].v[0],textures[b].v[1]);
                glNormal3fv(verts[b].vn);
                glVertex3fv(verts[b].v);
            	if(hasTexture) glTexCoord2f(textures[c].v[0],textures[c].v[1]);
                glNormal3fv(verts[c].vn);
                glVertex3fv(verts[c].v);
                if(in.peek() != '\n') {
                    in >> d;
            		if(hasTexture) glTexCoord2f(textures[d].v[0],textures[d].v[1]);
                    glNormal3fv(verts[d].vn);
                    glVertex3fv(verts[d].v);
                }
                //fprintf(stderr,"\nA:%f %f v %f %f %f\n", textures[a].v[0], textures[a].v[1], verts[a].v[0], verts[a].v[1], verts[a].v[2]);
                //fprintf(stderr,"B: %f %f v %f %f %f\n", textures[b].v[0], textures[b].v[1], verts[b].v[0], verts[b].v[1], verts[b].v[2]);
                //fprintf(stderr,"C: %f %f v%f %f %f\n", textures[c].v[0], textures[c].v[1], verts[c].v[0], verts[c].v[1], verts[c].v[2]);
                //fprintf(stderr,"D: %f %f v%f %f %f\n\n", textures[d].v[0], textures[d].v[1], verts[d].v[0], verts[d].v[1], verts[d].v[2]);
            glEnd();
            in.ignore(10,'\n');
        }else if(token.compare("MATERIAL") == 0) {
            in >> token;
            in.ignore(10,'\n');
            setMaterial(token,mats);
        }else if(token.compare("OBJECT") == 0) {
            // to rewind that we just read "OBJECT"
            in.unget().unget().unget().unget().unget().unget();
            break;
        }else if(token.compare(".////") == 0) {
			//file is finished
            break;
        }else
            fprintf(stderr,"Invalid syntax reading faces: '%s'.\n", token.c_str());
        in >> token;
    }
    glPopMatrix();
    glEndList();
    verts.clear();
}

//########################################################
void ModelPart::display() {
    glPushMatrix();
    glTranslatef(relativePosition.v[0],relativePosition.v[1],relativePosition.v[2]);
    glRotated(relativeRotation.v[0],0,0,1);
    glRotated(relativeRotation.v[1],0,1,0);
    glRotated(relativeRotation.v[2],1,0,0);
    glCallList(modelPartId);
    for(int i = 0; i < (int)modelPartList.size(); i++)
        modelPartList[i]->display();
    glPopMatrix();
}

//########################################################
void ModelPart::processImage(string filename){
	filename = "../data/models/textures/" + filename + ".tga";
	fprintf(stderr,"ModelPart::processImage '%s'\n",filename.c_str());
	ifstream File(filename.c_str(), ios::in | ios::binary);
	unsigned char header[20];
	if (!File){
		texImage.id=0;
		exit(0);
	}

	//read all 18 bytes of the header
	File.read (reinterpret_cast<char *>(header), sizeof (char)*18);
	//should be image type 2 (color) or type 10 (rle compressed color)
	if (header[2] != 2 && header[2] != 10){
		File.close();
		fprintf(stderr,"Texture image type error: not type 2 or 10\n");
		exit(0);
	}
	//if there is an image ID section then skip over it
	if (header[0]){
		File.seekg(header[0],ios_base::cur);
	}
	// get the size and bitdepth from the header
	texImage.width = header[13] * 256 + header[12];
	texImage.height = header[15] * 256 + header[14];
	texImage.bpp = header[16] / 8;
	
	if (texImage.bpp != 3 && texImage.bpp != 4){
		File.close();
		fprintf(stderr,"Texture image type error: size/bitdepth\n");
		exit(0);
	}
	
	//allocate memory for the texture
	long imageSize = texImage.width * texImage.height * texImage.bpp;
	//allocate memory for image data
	texImage.data = new unsigned char[imageSize];
	
	//read the uncompressed image data if type 2
	if (header[2] == 2){
		File.read(reinterpret_cast<char *>(texImage.data), sizeof (char)*imageSize);
	}
	long ctpixel=0,ctloop=0;
	
	//read the compressed image data if type 10
	if (header[2] == 10){
		// stores the rle header and the temp color data
		unsigned char rle;
		unsigned char color[4];
		while (ctpixel<imageSize){
			// reads the the RLE header
			File.read(reinterpret_cast<char *>(&rle), 1);
			// if the rle header is below 128 it means that what folows is just raw data with rle+1 pixels
			if (rle<128){
				File.read(reinterpret_cast<char *>(&texImage.data[ctpixel]), texImage.bpp*(rle+1));
				ctpixel+=texImage.bpp*(rle+1);
			}
			// if the rle header is equal or above 128 it means that we have a string of rle-127 pixels 
			// that use the folowing pixels color
			else {
				// read what color we should use
				File.read(reinterpret_cast<char *>(&color[0]), texImage.bpp);
				// insert the color stored in tmp into the folowing rle-127 pixels
				ctloop=0;
				while(ctloop<(rle-127))	{
					//TGA reads in bgr not rgb so...
					texImage.data[ctpixel]=color[0];
					texImage.data[ctpixel+1]=color[1];
					texImage.data[ctpixel+2]=color[2];
					if (texImage.bpp==4){
						texImage.data[ctpixel+3]=color[3];
					}
					ctpixel += texImage.bpp;
					ctloop++;
				}
			}
		}
	}
	ctpixel=0;
	//Because TGA file store their colors in BGRA format we need to swap the red and blue color components
	char tmp;
	while (ctpixel<imageSize){
		tmp = texImage.data[ctpixel];
		texImage.data[ctpixel] = texImage.data[ctpixel+2];
		texImage.data[ctpixel+2] = tmp;
		ctpixel += texImage.bpp;
	}
	//close file
	File.close();
}

//########################################################
void ModelPart::loadTexture(){
	//fprintf(stderr,"ModelPart::loadTexture '%s'\n", texImage.name.c_str());
	glGenTextures(1,&texImage.id );
	glBindTexture(GL_TEXTURE_2D, texImage.id);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, texImage.width, texImage.height, 
		0, GL_RGB, GL_UNSIGNED_BYTE, texImage.data);	
}//end loadTexture

//#######################################################
void ModelPart::freeTexture()
{
  glDeleteTextures( 1, &texImage.id ); 
}
